﻿/*
 * This file is part of CommunitySettlers.
 *
 * CommunitySettlers is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * CommunitySettlers is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with CommunitySettlers.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Author: Christoph Husse
 * 
 * Also checkout our homepage: http://communitysettlers.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Threading;
using System.IO;
using System.Collections.ObjectModel;
using SettlersEngine;

namespace GameHostService
{
    [DataContract]
    public class GameSetup
    {
        [DataMember]
        public ReadOnlyCollection<Guid> Players { get; private set; }
        [DataMember]
        public Guid Map { get; private set; }
        [DataMember]
        public TimeSpan PeaceTime { get; set; }

        public GameSetup(Guid inMap, params Guid[] inPlayers)
        {
            Players = new ReadOnlyCollection<Guid>(new List<Guid>(inPlayers));

            Validate();
        }

        public void Validate()
        {

        }
    }

    public class GameSession : IDisposable
    {
        private const Int32 MAX_PACKET_SIZE = 1024 * 12;

        private static System.Diagnostics.Stopwatch s_Watch = new System.Diagnostics.Stopwatch();
        private static Int64 m_SyncCounter = 0;

        private UniqueMap<Guid, PlayerInfo> m_JoinedPlayers = new UniqueMap<Guid, PlayerInfo>();
        private readonly Object m_Lock = new Object();

        public GameSetup Setup { get; private set; }
        public Guid GameID { get; private set; }
        public TimeSpan LastSyncTime{get; private set;}
        public Boolean HasStarted
        {
            get
            {
                return m_JoinedPlayers.Count == Setup.Players.Count;
            }
        }

        private class PlayerInfo
        {
            public PlayerSession Player { get; private set; }
            public LinkedList<byte[]> IncomingPackets{get; private set;}
            public List<GameHostEvent> IncomingEvents { get; private set; }

            public PlayerInfo(PlayerSession inPlayer)
            {
                Player = inPlayer;
                IncomingPackets = new LinkedList<byte[]>();
                IncomingEvents = new List<GameHostEvent>();
            }
        }

        static GameSession()
        {
            s_Watch.Start();
        }

        public GameSession(GameSetup inSetup)
        {
            if (inSetup == null)
                throw new ArgumentNullException();

            Setup = inSetup;

            GameID = Guid.NewGuid();
            LastSyncTime = s_Watch.Elapsed;
        }

        public void Dispose()
        {
            GameID = Guid.Empty;
            LastSyncTime = TimeSpan.FromMilliseconds(0);
        }

        /// <summary>
        /// Send packets for the current client session back to client.
        /// </summary>
        public byte[] FetchPacket(PlayerSession inSession, out TimeSpan outServerTime, out List<GameHostEvent> outEvents)
        {
            MemoryStream finalResult = new MemoryStream();

            lock (m_Lock)
            {
                PlayerInfo player = m_JoinedPlayers[inSession.MaskedID];

                // fetch events
                outEvents = player.IncomingEvents.ToList();
                player.IncomingEvents.Clear();

                if (outEvents.Count > 0)
                {
                }

                // send incoming packets back to client
                if (player.IncomingPackets.Count > 0)
                {
                    Int32 byteCount = 0;
                    LinkedListNode<byte[]> packet = player.IncomingPackets.First;

                    while (packet != null)
                    {
                        LinkedListNode<byte[]> next = packet.Next;

                        if (byteCount > MAX_PACKET_SIZE)
                            break;

                        player.IncomingPackets.Remove(packet);
                        finalResult.Write(packet.Value, 0, packet.Value.Length);

                        byteCount += packet.Value.Length;
                        packet = next;
                    }
                }

                outServerTime = s_Watch.Elapsed;
            }

            return finalResult.ToArray();
        }

        public void JoinPlayer(PlayerSession inSession)
        {
           // lock (m_Lock)
            {
                if (m_JoinedPlayers.Count(e => e.Value.Player.MaskedID == inSession.MaskedID) > 0)
                    throw new InvalidOperationException("Player has been added already.");

                if (!Setup.Players.Contains(inSession.MaskedID))
                    throw new InvalidOperationException("Player is not setup to join this game.");

                m_JoinedPlayers.Add(inSession.MaskedID, new PlayerInfo(inSession));

                inSession.Game = this;

                BroadcastEvent(GameHostEventType.PlayerJoined, inSession.MaskedID);

                if (m_JoinedPlayers.Count == Setup.Players.Count)
                    StartGame();
            }
        }

        private void BroadcastEvent(GameHostEventType inType, Guid inOptionalID)
        {
            lock (m_Lock)
            {
                foreach (var player in m_JoinedPlayers.Values)
                {
                    player.IncomingEvents.Add(new GameHostEvent()
                    {
                        Type = inType,
                        PlayerID = inOptionalID,
                    });
                }
            }
        }

        private void StartGame()
        {
            BroadcastEvent(GameHostEventType.GameStarted, Guid.Empty);
        }

        public void Broadcast(PlayerSession inSender, byte[] inData)
        {
            lock (m_Lock)
            {
                LastSyncTime = s_Watch.Elapsed;

                // broadcast incoming data to all players
                if (inData != null)
                {
                    Int32 cmdCount = NetworkProtocol.GetCommandCount(inData);
                    Int64 syncCounter = Interlocked.Add(ref m_SyncCounter, cmdCount);

                    NetworkProtocol.Stamp(inSender.MaskedID, LastSyncTime.Ticks, syncCounter, inData);

                    foreach (var player in m_JoinedPlayers.Values)
                    {
                        player.IncomingPackets.AddLast(inData);
                    }
                }
            }
        }
    }
}